#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "xmlrpcbase64.h"
#include "xmlrpcconnection.h"
#include "xmlrpcintrospect.h"
#include "xmlrpcparser.h"
#include "xmlrpcrequest.h"
#include "xmlrpcresponse.h"

#include <QDomDocument>
#include <QFileDialog>
#include <QList>
#include <QMessageBox>
#include <QUrl>
#include <QVariant>

using XmlRpc::XmlRpcConnection;
using XmlRpc::XmlRpcRequest;
using XmlRpc::XmlRpcIntrospect;
using XmlRpc::XmlRpcBase64;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow), m_con(new XmlRpcConnection(this)),
    m_introspect(new XmlRpcIntrospect(m_con))
{
    ui->setupUi(this);
    connect(m_con, SIGNAL(finished(XmlRpc::XmlRpcResponse*)),
            this, SLOT(rpcQueryCompleted(XmlRpc::XmlRpcResponse*)));
    connect(m_introspect, SIGNAL(methodHelpReceived(QString)), this,
            SLOT(methodHelpReceived(QString)));
    connect(m_introspect, SIGNAL(methodListReceived(QStringList)), this,
            SLOT(methodListReceived(QStringList)));
    connect(m_introspect, SIGNAL(methodSignatureReceived(QVariant)), this,
            SLOT(methodSignatureReceived(QVariant)));
    if (updateUrl())
        m_introspect->listMethods();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Return && event->modifiers()  == Qt::ControlModifier)
        on_executeButton_clicked();
    else
        QMainWindow::keyPressEvent(event);
}

QList<QVariant> MainWindow::parseParams(const QString &text)
{
    QList<QVariant> ret;
    QDomDocument doc;
    QString errorMsg;
    int errorLine, errorCol;
    if (!doc.setContent(text, &errorMsg, &errorLine, &errorCol)) {
        QMessageBox errorMsg;
        errorMsg.setText(tr("Invalid params: bad XML."));
        errorMsg.exec();
        return ret;
    }
    QDomElement root = doc.documentElement();
    if (root.tagName() != "params" || !root.hasChildNodes()) {
        QMessageBox errorMsg;
        errorMsg.setText(tr("Invalid params: the root node must be 'params'."));
        errorMsg.exec();
        return ret;
    }
    QDomElement param = root.firstChildElement("param");
    while (!param.isNull()) {
        XmlRpc::XmlRpcParser parser;
        ret << parser.parseParam(param);
        param = param.nextSiblingElement("param");
    }
    return ret;
}

void MainWindow::displayResult(const QVariant &result, const QString &header)
{
    QString text;
    switch (result.type()) {
    case QVariant::List: {
            QList<QVariant> values = result.toList();
            QVariant var;
            foreach (var, values) {
                text += var.toString() + "\n";
            }
        }
        break;
    case QVariant::Map: {
            QMap<QString, QVariant> m = result.toMap();
            QString key;
            foreach (key, m.keys()) {
                text += "[ " + key;
                text += " = " + m.value(key).toString() + "] \n";
            }
        }
        break;
    case QVariant::UserType:
        if (qVariantCanConvert<XmlRpcBase64>(result)) {
            XmlRpcBase64 base64 = qVariantValue<XmlRpcBase64>(result);
            text += tr("BASE64: ") + base64.decoded();
            break;
        }
        // Fall through
    default:
        text += result.toString();
        break;
    }
    if (!header.isEmpty()) {
        text.prepend("\n");
        text.prepend(header);
    }
    ui->responseEdit->setPlainText(text);
}

bool MainWindow::updateUrl()
{
    QUrl url(ui->urlEdit->text());
    if (!url.isValid())
        return false;
    m_con->setUrl(ui->urlEdit->text().trimmed());
    return true;
}

void MainWindow::on_browseButton_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Select parameters file"),
                                                    tr("Files of any type (*)"));
    ui->filenameEdit->setText(filename);
}

void MainWindow::on_executeButton_clicked()
{
    if (updateUrl()) {
        XmlRpcRequest request;
        request.setMethod(ui->methodCombo->currentText().trimmed());
        QString params = ui->paramsEdit->document()->toPlainText().trimmed();
        if (params.isEmpty()) {
            request.setParams(QList<QVariant>());
        } else {
            QList<QVariant> paramList(parseParams(params));
            if (!paramList.isEmpty())
                request.setParams(paramList);
            else
                return;
        }
        m_con->query(request);
    } else {
        QMessageBox errorMsg;
        errorMsg.setText(tr("Invalid URL."));
        errorMsg.exec();
    }
}

void MainWindow::on_helpButton_clicked()
{
    if (updateUrl())
        m_introspect->methodHelp(ui->methodCombo->currentText().trimmed());
}

void MainWindow::on_loadButton_clicked()
{
    QString filename = ui->filenameEdit->text();
    QFile f(filename);
    if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox errorMsg;
        errorMsg.setText(QString(tr("Failed to open %1.")).arg(filename));
        errorMsg.exec();
    }
    ui->paramsEdit->clear();
    ui->paramsEdit->setPlainText(f.readAll());
    f.close();
}

void MainWindow::on_refreshButton_clicked()
{
    if (updateUrl())
        m_introspect->listMethods();
}

void MainWindow::on_signatureButton_clicked()
{
    if (updateUrl())
        m_introspect->methodSignature(ui->methodCombo->currentText().trimmed());
}

void MainWindow::on_filenameEdit_textChanged(QString newText)
{
    ui->loadButton->setEnabled(!newText.trimmed().isEmpty());
}

void MainWindow::methodHelpReceived(const QString &helpText)
{
    ui->labelHelp->setText(helpText);
}

void MainWindow::methodListReceived(QStringList methods)
{
    ui->methodCombo->clear();
    foreach (QString method, methods) {
        ui->methodCombo->addItem(method);
    }
}

void MainWindow::methodSignatureReceived(const QVariant &signature)
{
    displayResult(signature, tr("Method signature:"));
}

void MainWindow::rpcQueryCompleted(XmlRpc::XmlRpcResponse *response)
{
    if (response->error())
        ui->responseEdit->setPlainText(QString(tr("ERROR (%1): %2")).arg(response->errorCode())
                                       .arg(response->errorString()));
    else
        displayResult(response->returnValue());

    ui->rawResponseEdit->setPlainText(response->rawResponse());
    response->deleteLater();
}
